package org.hertsig.entities;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Map;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;

@AllArgsConstructor(access=AccessLevel.PACKAGE)
public class ManaCost {
	public static final ManaCost NONE = new ManaCost(ImmutableList.<ManaSymbol>of());
	@Getter(value=AccessLevel.PRIVATE, lazy=true) private static final Map<String, ManaSymbol> symbolIndex = initSymbolIndex();
	
	private static Map<String, ManaSymbol> initSymbolIndex() {
		Builder<String, ManaSymbol> builder = ImmutableMap.builder();
		for (Symbol symbol : Symbol.values()) {
			builder.put(symbol.getNotation(), symbol);
		}
		for (int size = 0; size <= 16; size++) {
			builder.put(Integer.toString(size), new ColorlessSymbol(size));
		}
		return builder.build();
	}
	
	public static ManaCost parse(String cost) {
		if (cost.isEmpty()) {
			return NONE;
		}
		
		ImmutableList.Builder<ManaSymbol> result = ImmutableList.builder();
		for (int i = 0; i < cost.length(); i++) {
			if (cost.charAt(i) == '{') {
				String symbol = cost.substring(i + 1, cost.indexOf('}', i));
				result.add(get(symbol));
				i += symbol.length() + 1;
			}
			else {
				result.add(get(cost.substring(i, i+1)));
			}
		}
		
		ImmutableList<ManaSymbol> symbols = result.build();
		checkArgument(!symbols.isEmpty(), cost);
		return new ManaCost(symbols);
	}
	
	public static ManaSymbol get(String symbol) {
		return checkNotNull(getSymbolIndex().get(symbol), "No symbol %s", symbol);
	}
	
	public static ManaSymbol colorless(int size) {
		return get(Integer.toString(size));
	}
	
	private final ImmutableList<ManaSymbol> symbols;
	
	public int convertedManaCost() {
		return symbols.stream().map(ManaSymbol::getConverted).reduce(0, (a, b) -> a+b);
	}
	
	public boolean isWhite() {
		return symbols.stream().anyMatch(ManaSymbol::isWhite);
	}
	
	public boolean isBlue() {
		return symbols.stream().anyMatch(ManaSymbol::isBlue);
	}
	
	public boolean isBlack() {
		return symbols.stream().anyMatch(ManaSymbol::isBlack);
	}
	
	public boolean isRed() {
		return symbols.stream().anyMatch(ManaSymbol::isRed);
	}
	
	public boolean isGreen() {
		return symbols.stream().anyMatch(ManaSymbol::isGreen);
	}
	
	public boolean isColorless() {
		return symbols.stream().allMatch(ManaSymbol::isColorless);
	}
	
	public int getColors() {
		return n(isWhite()) + n(isBlue()) + n(isBlack()) + n(isRed()) + n(isGreen());
	}
	
	private static int n(boolean b) {
		return b ? 1 : 0;
	}
	
	@Override
	public String toString() {
		return Joiner.on("").join(symbols.stream().map(e -> "{" + e.getNotation() + "}").iterator());
	}
	
	public interface ManaSymbol {
		String getNotation();
		int getConverted();
		boolean isWhite();
		boolean isBlue();
		boolean isBlack();
		boolean isRed();
		boolean isGreen();
		
		default boolean isColorless() {
			return !isWhite() && !isBlue() &&!isBlack() &&!isRed() &&!isGreen();
		}
	}
	
	@AllArgsConstructor @Getter
	private enum Symbol implements ManaSymbol {
		W("W"), U("U"), B("B"), R("R"), G("G"),
		X("X", 0), Y("Y", 0),
		HYBRID_WU("W/U"), HYBRID_WB("W/B"), HYBRID_UB("U/B"), HYBRID_UR("U/R"), HYBRID_BR("B/R"), HYBRID_BG("B/G"), HYBRID_RG("R/G"), HYBRID_RW("R/W"), HYBRID_GW("G/W"), HYBRID_GU("G/U"),
		HYBRID_2W("2/W", 2), HYBRID_2U("2/U", 2), HYBRID_2B("2/B", 2), HYBRID_2R("2/R", 2), HYBRID_2G("2/G", 2),
		PHYREXIAN_W("WP"), PHYREXIAN_U("UP"), PHYREXIAN_B("BP"), PHYREXIAN_R("RP"), PHYREXIAN_G("GP"), 
		SNOW("S", 1);
		
		private final String notation;
		private final int converted;
		
		private Symbol(String symbol) {
			this(symbol, 1);
		}
		
		@Override
		public boolean isWhite() {
			return notation.contains("W");
		}
		
		@Override
		public boolean isBlue() {
			return notation.contains("U");
		}
		
		@Override
		public boolean isBlack() {
			return notation.contains("B");
		}
		
		@Override
		public boolean isRed() {
			return notation.contains("R");
		}
		
		@Override
		public boolean isGreen() {
			return notation.contains("G");
		}
		
		@Override
		public String toString() {
			return "{" + notation + "}";
		}
	}
	
	@Getter @AllArgsConstructor(access=AccessLevel.PRIVATE)
	private static class ColorlessSymbol implements ManaSymbol {
		private final int size;
		
		@Override
		public String getNotation() {
			return Integer.toString(size);
		}
		
		@Override
		public int getConverted() {
			return size;
		}
		
		@Override
		public boolean isWhite() {
			return false;
		}
		
		@Override
		public boolean isBlue() {
			return false;
		}
		
		@Override
		public boolean isBlack() {
			return false;
		}
		
		@Override
		public boolean isRed() {
			return false;
		}
		
		@Override
		public boolean isGreen() {
			return false;
		}
	}
}
